在 ASP.NET 使用 Autofac 實作 DI
TLDR
- Autofac 是 ASP.NET 生態系中強大的 DI 套件,支援多種框架整合。
- 預設生命週期為
InstancePerDependency(Transient),可依需求調整為InstancePerLifetimeScope(Scoped) 或SingleInstance(Singleton)。 - 透過
RegisterAssemblyTypes可利用 Reflection 進行大量型別註冊,減少手動設定。 - 循環依賴 (Circular Dependencies) 需透過
PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies)啟用 Property Injection 解決。 - 在 MVC 與 Web API 中,需透過
DependencyResolver或DependencyResolver設定將 Autofac 注入至 Controller。 - Web Form 與 Web Service 因不支援 Constructor Injection,必須改用 Property Injection。
- 建議將 AppSettings 封裝為 Options Class 並透過 Autofac 註冊,以利型別轉換與單元測試。
Autofac 基礎概念與註冊
Autofac 會在每個 Request 建立一個 Lifetime Scope。註冊型別時,使用 RegisterType<{Instance Type}>().As({Declare Type}) 來定義對應關係。
使用 Reflection 大量註冊
若要避免逐一註冊,可使用 RegisterAssemblyTypes 搜尋 Assembly 下的特定型別:
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(x => typeof(IAppService).IsAssignableFrom(x));指定註冊型別的對應方式
As():註冊給指定型別使用。AsImplementedInterfaces():註冊給自身實作的 Interface 使用。AsClosedTypesOf(open):註冊給開放泛型型別的封閉實例使用。AsSelf():註冊給自身使用(預設值)。
TIP
若已指定 AsImplementedInterfaces() 等設定,Autofac 將不會自動加入 AsSelf(),若有需要請手動補上。
Instance Scope 管理
Autofac 提供三種主要的生命週期管理方式:
| Instance Scope | 描述 | .NET Core 對應 |
|---|---|---|
| Instance Per Dependency | 每次呼叫產生新 Instance | Transient |
| Instance Per Lifetime Scope | 每個 Scope 產生一個 Instance | Scoped |
| Single Instance | 整個 Container 共用一個 Instance | Singleton |
處理循環依賴 (Circular Dependencies)
什麼情況下會遇到這個問題:當兩個類別互相依賴(例如 Main 包含 Sub,Sub 也包含 Main)時,無法透過 Constructor Injection 完成實例化。
解決方式是改用 Property Injection 並設定允許循環依賴:
builder.RegisterType<Main>()
.InstancePerLifetimeScope();
builder.RegisterType<Sub>()
.InstancePerLifetimeScope()
.PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);WARNING
- 參與循環依賴的型別不可使用
InstancePerDependency()。 - 若無循環依賴需求,請勿傳入
PropertyWiringOptions.AllowCircularDependencies。
在 MVC 與 Web API 使用 Autofac
在 MVC 或 Web API 專案中,必須在 Global.asax.cs 中註冊 Controller 並設定 DependencyResolver。
MVC 範例
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// 註冊服務
builder.RegisterType<AppService>().As<IAppService>().InstancePerLifetimeScope();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));模擬 ASP.NET Core 的 FromServices
若希望在 Action 參數中使用 [FromServices],可實作 IModelBinder:
public class ServicesModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
return DependencyResolver.Current.GetService(bindingContext.ModelType);
}
}封裝 AppSettings 為 Options 注入
什麼情況下會遇到這個問題:直接使用 WebConfigurationManager 導致型別轉換繁瑣,且因靜態類別特性不利於單元測試。
建議將設定封裝為 Options Class,並透過 Module 進行註冊:
private void RegisterOptions<T>(ContainerBuilder builder) where T : class {
string optionsName = typeof(T).Name.Replace("Options", "");
var registrationBuilder = builder.RegisterType<T>().AsSelf().InstancePerLifetimeScope();
foreach (string key in WebConfigurationManager.AppSettings.AllKeys.Where(x => x.StartsWith(optionsName))) {
registrationBuilder.WithParameter(new ResolvedParameter(
(pi, ctx) => pi.Name.Equals(Regex.Replace(key, $@"^{optionsName}:", "", RegexOptions.IgnoreCase), StringComparison.OrdinalIgnoreCase),
(pi, ctx) => Convert.ChangeType(WebConfigurationManager.AppSettings[key], pi.ParameterType)));
}
}Web Form 與 Web Service 的特殊處理
什麼情況下會遇到這個問題:Web Form 與 Web Service 不支援 Constructor Injection。
必須改用 Property Injection。在 Web Form 中需於 Web.config 設定 PropertyInjectionModule;在 Web Service 中則需建立 WebServiceBase 並在建構式中手動注入:
public abstract class WebServiceBase : System.Web.Services.WebService {
public WebServiceBase() {
IContainerProviderAccessor cpa = (IContainerProviderAccessor)HttpContext.Current.ApplicationInstance;
cpa.ContainerProvider.RequestLifetime.InjectProperties(this);
}
}異動歷程
- 2022-11-05 初版文件建立。
